package com.fhao.job.handler;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fhao.common.core.constants.CacheConstants;
import com.fhao.common.core.constants.Constants;
import com.fhao.common.core.enums.ExamListType;
import com.fhao.common.redis.service.RedisService;
import com.fhao.job.domain.exam.Exam;
import com.fhao.job.domain.message.Message;
import com.fhao.job.domain.message.MessageText;
import com.fhao.job.domain.message.vo.MessageTextVO;
import com.fhao.job.domain.user.UserScore;
import com.fhao.job.mapper.exam.ExamMapper;
import com.fhao.job.mapper.message.MessageTextMapper;
import com.fhao.job.mapper.user.UserExamMapper;
import com.fhao.job.mapper.user.UserSubmitMapper;
import com.fhao.job.service.IMessageService;
import com.fhao.job.service.IMessageTextService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;


@Component
@Slf4j
public class ExamXxlJob {

    @Autowired
    private RedisService redisService;

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private UserSubmitMapper userSubmitMapper;

    @Autowired
    private MessageTextMapper messageTextMapper;

    @Autowired
    private IMessageTextService messageTextService;

    @Autowired
    private IMessageService messageService;

    @Autowired
    private UserExamMapper userExamMapper;

    @XxlJob("examListOrganizeHandler")
    public void  examListOrganizeHandler() {
        //统计哪些竞赛应该存入未完赛的列表中 哪些竞赛应该存入历史竞赛列表中, 统计出来之后, 再存入对应的缓存中
        log.info("*** examListOrganizeHandler ***");
        //1. 重新刷新 redis缓存中未完赛的竞赛
        List<Exam> unFinishList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                .gt(Exam::getEndTime, LocalDateTime.now())
                .eq(Exam::getStatus, Constants.TRUE)
                .orderByDesc(Exam::getCreateTime));
        refreshCache(unFinishList,CacheConstants.EXAM_UNFINISHED_LIST);
        //2. 重新刷新 redis缓存中已完赛的历史竞赛
        List<Exam> historyList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                .le(Exam::getEndTime, LocalDateTime.now())
                .eq(Exam::getStatus, Constants.TRUE)
                .orderByDesc(Exam::getCreateTime));
        refreshCache(historyList,CacheConstants.EXAM_HISTORY_LIST);
    }

    //获取消息列表
    @XxlJob("examResultHandler")
    public void examResultHandler() {
        //排名排好消息自然就有了
        LocalDateTime now = LocalDateTime.now();//当前时间 10.27 15:00
        LocalDateTime minusDatetime = now.minusDays(1);//当前时间的前一天 10.26 15:00
        List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                        .select(Exam::getExamId, Exam::getTitle) //只需查出竞赛id
                .eq(Exam::getStatus, Constants.TRUE)// 确保竞赛已发布
                .ge(Exam::getEndTime, minusDatetime)//getEndTime >= minusDatetime
                .le(Exam::getEndTime, now));//getEndTime <= now
        //判空处理
        if(CollectionUtil.isEmpty(examList)) {
            return;
        }
        Set<Long> examIdSet = examList.stream().map(Exam::getExamId).collect(Collectors.toSet());
        List<UserScore> userScoreList = userSubmitMapper.selectUserScoreList(examIdSet);
        //userScoreList 可能存在多个竞赛, 不同竞赛之间排名没有意义, 应该将同一竞赛的排名拿出来
        //将不同的竞赛区分开
        Map<Long, List<UserScore>> userScoreMap = userScoreList.stream().collect(Collectors.groupingBy(UserScore::getExamId));
        createMessage(examList,userScoreMap);
    }

    private void createMessage(List<Exam> examList, Map<Long, List<UserScore>> userScoreMap) {
        List<MessageText> messageTextList = new ArrayList<>();
        List<Message> messageList = new ArrayList<>();
        for (Exam exam : examList) {
            Long examId = exam.getExamId();
            List<UserScore> userScoreList = userScoreMap.get(examId);
            int totalUser = userScoreList.size();
            int examRank = 1;
            //生成消息
            for (UserScore userScore : userScoreList) {
                String msgTitle = exam.getTitle()+"--排名情况";
                String msgContent = "您参与的竞赛: "+exam.getTitle()+
                        ". 本次参与竞赛一共"+totalUser+"人. 您排名第"+examRank+"名!";
                userScore.setExamRank(examRank);
                MessageText messageText = new MessageText();
                messageText.setMessageTitle(msgTitle);
                messageText.setMessageContent(msgContent);
                messageText.setCreateBy(Constants.SYSTEM_USER_ID);//消息由系统生成
//                messageTextMapper.insert(messageText); //在循环中插入数据库, 性能低, 可以采取批量操作只与数据库交互一次.
                messageTextList.add(messageText);
                Message message = new Message();
                message.setSendId(Constants.SYSTEM_USER_ID);
                message.setCreateBy(Constants.SYSTEM_USER_ID);
                message.setRecId(userScore.getUserId());
                messageList.add(message);
                examRank++;
            }
            userExamMapper.updateUserScoreAndRank(userScoreList);//将userExam表的 得分和排名更新一下
            //更新竞赛排名缓存
            String examRankListKey = getExamRankListKey(examId);
            redisService.rightPushAll(examRankListKey, userScoreList);
        }
        messageTextService.batckInsert(messageTextList); //一次性批量插入消息内容表
        Map<String, MessageTextVO> messageTextVOMap = new HashMap<>();
        for (int i = 0; i < messageTextList.size(); i++) {
            MessageText messageText = messageTextList.get(i);
            MessageTextVO messageTextVO = new MessageTextVO();
            BeanUtil.copyProperties(messageText,messageTextVO);
            String msgDetailKey = getMsgDetailKey(messageText.getTextId());
            messageTextVOMap.put(msgDetailKey, messageTextVO);
//            redisService.setCacheObject();//在循环中插入redis缓存, 性能低, 可以采取批量操作只与redis交互一次.
            Message message = messageList.get(i);
            message.setTextId(messageText.getTextId());
        }
        messageService.batckInsert(messageList); //一次性批量插入消息表
        //关键信息存入redis缓存

        //拿到每个用户的消息表
        Map<Long, List<Message>> userMsgMap = messageList.stream().collect(Collectors.groupingBy(Message::getRecId));
        Iterator<Map.Entry<Long, List<Message>>> iterator = userMsgMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Long, List<Message>> entry = iterator.next();
            Long recId = entry.getKey();
            //entry.getValue()拿到的是List<Message>> 还需要把它转成 List<Long> 拿到所有的textId即可.
            List<Long> userMsgTextIdList = entry.getValue().stream().map(Message::getTextId).toList();
            String userMsgListKey = getUserMsgListKey(recId);
            redisService.rightPushAll(userMsgListKey, userMsgTextIdList);//用户消息列表存入redis缓存.
        }
        redisService.multiSet(messageTextVOMap);//一次性批量存储 messageTextVO
    }

    //刷新缓存逻辑
    public void refreshCache(List<Exam> examList,String examListKey) {
        if (CollectionUtil.isEmpty(examList)) {
            return;
        }

        Map<String, Exam> examMap = new HashMap<>();
        List<Long> examIdList = new ArrayList<>();
        for (Exam exam : examList) {
            examMap.put(getDetailKey(exam.getExamId()), exam);
            examIdList.add(exam.getExamId());
        }
        redisService.multiSet(examMap);  //刷新详情缓存
        redisService.deleteObject(examListKey);//由于下面调用了尾插法, 为了避免重复, 先清除所有竞赛id.
        redisService.rightPushAll(examListKey, examIdList); //刷新列表缓存(尾插法,将所有的竞赛id存到缓存中)
    }

    private String getExamListKey(Integer examListType) {
        if (ExamListType.EXAM_UN_FINISH_LIST.getValue().equals(examListType)) {
            return CacheConstants.EXAM_UNFINISHED_LIST;
        } else if (ExamListType.EXAM_HISTORY_LIST.getValue().equals(examListType)) {
            return CacheConstants.EXAM_HISTORY_LIST;
        } else {
            return "";
        }
    }

    private String getDetailKey(Long examId) {
        return CacheConstants.EXAM_DETAIL + examId;
    }

    private String getUserMsgListKey(Long userId) {
        return CacheConstants.USER_MESSAGE_LIST+userId;
    }

    private String getMsgDetailKey(Long textId) {
        return CacheConstants.MESSAGE_DETAIL+textId;
    }

    private String getExamRankListKey(Long examId) {
        return CacheConstants.EXAM_RANK_LIST+examId;
    }
}











